package run.microservice.stock.utils;

import lombok.extern.slf4j.Slf4j;

import java.net.NetworkInterface;
import java.util.Enumeration;
import java.util.Random;
import java.util.concurrent.atomic.AtomicLong;

/**
 * ID生成器。此ID生成器是Snowflake算法改良版的Java实现，把机器位和时间戳换位。在单节点里面，它肯定是单调递增的，但是如果是多个节点，则不是全局递增
 * 来源：https://github.com/apache/incubator-seata/blob/2.x/common/src/main/java/org/apache/seata/common/util/IdWorker.java
 *
 * 原雪花算法：
 * 0-00000000000000000000000000000000000000000-0000000000-000000000000
 * 恒为0（1位）
 * 时间戳（41位）
 * 节点ID（10位）
 * 序列号（12位）
 *
 * 改成：
 * 0-0000000000-00000000000000000000000000000000000000000-000000000000
 * 恒为0（1位）
 * 节点ID（10位）
 * 时间戳（41位）
 * 序列号（12位）
 *
 * @author lunfy
 * @description:
 * @date 2024-04-23 17:32
 */
@Slf4j
public class SnowflakeImproveWorker {

    private SnowflakeImproveWorker() {}

    private static class SingletonHolder {
        private static final SnowflakeImproveWorker INSTANCE = new SnowflakeImproveWorker();
    }
    public static SnowflakeImproveWorker getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 防止序列化破坏单例 */
    protected Object readResolve() {
        return SingletonHolder.INSTANCE;
    }

    /**
     * Start time cut (2024-01-01 00:00:00)
     */
    private final long twepoch = 1704038400000L;

    /**
     * The number of bits occupied by workerId
     */
    private final int workerIdBits = 10;

    /**
     * The number of bits occupied by timestamp
     */
    private final int timestampBits = 41;

    /**
     * The number of bits occupied by sequence
     */
    private final int sequenceBits = 12;

    /**
     * mask that help to extract timestamp and sequence from a long
     */
    private final long timestampAndSequenceMask = ~(-1L << (timestampBits + sequenceBits));

    /**
     * Maximum supported machine id, the result is 1023
     */
    private final int maxWorkerId = ~(-1 << workerIdBits);

    /**
     * business meaning: machine ID (0 ~ 1023)
     * actual layout in memory:
     * highest 1 bit: 0
     * middle 10 bit: workerId
     * lowest 53 bit: all 0
     */
    private long workerId;

    /**
     * timestamp and sequence mix in one Long
     * highest 11 bit: not used
     * middle  41 bit: timestamp
     * lowest  12 bit: sequence
     */
    private AtomicLong timestampAndSequence;

    /**
     * instantiate an SnowflakeImproveWorker using given workerId
     * @param workerId if null, then will auto assign one
     */
    public SnowflakeImproveWorker(Long workerId) {
        initTimestampAndSequence();
        initWorkerId(workerId);
    }

    /**
     * init first timestamp and sequence immediately
     */
    private void initTimestampAndSequence() {
        long timestamp = getNewestTimestamp();
        long timestampWithSequence = timestamp << sequenceBits;
        this.timestampAndSequence = new AtomicLong(timestampWithSequence);
    }

    /**
     * init workerId
     * @param workerId if null, then auto generate one
     */
    private void initWorkerId(Long workerId) {
        if (workerId == null) {
            workerId = generateWorkerId();
        }
        if (workerId > maxWorkerId || workerId < 0) {
            String message = String.format("worker Id can't be greater than %d or less than 0", maxWorkerId);
            throw new IllegalArgumentException(message);
        }
        this.workerId = workerId << (timestampBits + sequenceBits);
    }

    /**
     * get next ID(base on snowflake algorithm), which look like:
     * highest 1 bit: always 0
     * next   10 bit: workerId
     * next   41 bit: timestamp
     * lowest 12 bit: sequence
     * @return UUID
     */
    public long nextId() {
        waitIfNecessary();
        long next = timestampAndSequence.incrementAndGet();
        long timestampWithSequence = next & timestampAndSequenceMask;
        return workerId | timestampWithSequence;
    }

    /**
     * block current thread if the QPS of acquiring UUID is too high
     * that current sequence space is exhausted
     */
    private void waitIfNecessary() {
        long currentWithSequence = timestampAndSequence.get();
        long current = currentWithSequence >>> sequenceBits;
        long newest = getNewestTimestamp();
        if (current >= newest) {
            try {
                Thread.sleep(5);
            } catch (InterruptedException ignore) {
                // don't care
            }
        }
    }

    /**
     * get newest timestamp relative to twepoch
     */
    private long getNewestTimestamp() {
        // 因为雪花算法41位时间戳最多能用到2039年，因此减去一个时间后，可以大大延长生成器的可用时间
        return System.currentTimeMillis() - twepoch;
    }

    /**
     * auto generate workerId, try using mac first, if failed, then randomly generate one
     * @return workerId
     */
    private long generateWorkerId() {
        try {
            return generateWorkerIdBaseOnMac();
        } catch (Exception e) {
            return generateRandomWorkerId();
        }
    }

    /**
     * use lowest 10 bit of available MAC as workerId
     * @return workerId
     * @throws Exception when there is no available mac found
     */
    private long generateWorkerIdBaseOnMac() throws Exception {
        Enumeration<NetworkInterface> all = NetworkInterface.getNetworkInterfaces();
        while (all.hasMoreElements()) {
            NetworkInterface networkInterface = all.nextElement();
            boolean isLoopback = networkInterface.isLoopback();
            boolean isVirtual = networkInterface.isVirtual();
            byte[] mac = networkInterface.getHardwareAddress();
            if (isLoopback || isVirtual || mac == null) {
                continue;
            }

            // 使用 mac[4] 的前5位和 mac[5] 的前6位进行计算；最终结果值最大为 1023
            // ((3 << 8) | 255) = 3 * 256 + 255 = 768 + 255 = 1023。
            return ((mac[4] & 0B11) << 8) | (mac[5] & 0xFF);
        }
        throw new RuntimeException("no available mac found");
    }

    /**
     * randomly generate one as workerId
     * @return workerId
     */
    private long generateRandomWorkerId() {
        return new Random().nextInt(maxWorkerId + 1);
    }

    public static void main(String[] args) throws Exception {

        SnowflakeImproveWorker snowflakeImproveWorker = new SnowflakeImproveWorker(1L);
        log.info("workerId：{}", snowflakeImproveWorker.workerId);
        log.info("maxWorkerId：{}", ~(-1 << snowflakeImproveWorker.workerIdBits));
        log.info("generateWorkerIdBaseOnMac：{}", snowflakeImproveWorker.generateWorkerIdBaseOnMac());
        log.info("timestampAndSequenceMask：{}", ~(-1L << (snowflakeImproveWorker.timestampBits + snowflakeImproveWorker.sequenceBits)));

        log.info("nextId：{}", snowflakeImproveWorker.nextId());
        log.info("nextId1：{}", snowflakeImproveWorker.nextId());
    }

}
